<?php

namespace app\common\mqtt;

use PhpMqtt\Client\ConnectionSettings;
use PhpMqtt\Client\Exceptions\MqttClientException;
use PhpMqtt\Client\MqttClient;

class MqttService
{
    /** @var MqttClient|null */
    protected $client;

    /** @var array */
    protected $config = [];

    /**
     * @param array|null $config 可覆盖的配置
     */
    public function __construct(?array $config = null)
    {
        $this->config = array_merge(config('mqtt') ?: [], $config ?: []);
    }

    /**
     * 创建并返回底层 MqttClient（懒加载）
     * @return MqttClient
     */
    protected function getClient(): MqttClient
    {
        if ($this->client instanceof MqttClient) {
            return $this->client;
        }

        $clientId = $this->config['client_id'] ?: ('tp8_' . bin2hex(random_bytes(4)));

        // 使用 MQTT 3.1.1 版本，兼容性较好
        $this->client = new MqttClient(
            $this->config['host'],
            (int) $this->config['port'],
            $clientId,
            MqttClient::MQTT_3_1_1
        );

        return $this->client;
    }

    /**
     * 连接到 MQTT 服务器
     * @throws MqttClientException
     */
    public function connect(): void
    {
        $client = $this->getClient();

        $settings = (new ConnectionSettings())
            ->setUsername((string) ($this->config['username'] ?? ''))
            ->setPassword((string) ($this->config['password'] ?? ''))
            ->setKeepAliveInterval((int) ($this->config['keepalive'] ?? 60))
            ->setLastWillTopic(null)
            ->setLastWillMessage(null)
            ->setLastWillQualityOfService(0)
            ->setRetainLastWill(false);

        // TLS 配置（按需）
        if (!empty($this->config['use_tls'])) {
            $settings->setUseTls(true)
                ->setTlsVerifyPeer((bool) ($this->config['tls_verify_peer'] ?? false))
                ->setTlsSelfSignedAllowed(!(bool) ($this->config['tls_verify_peer'] ?? false));

            if (!empty($this->config['tls_ca_file'])) {
                $settings->setTlsCertificateAuthorityFile($this->config['tls_ca_file']);
            }
            if (!empty($this->config['tls_cert_file'])) {
                $settings->setTlsClientCertificateFile($this->config['tls_cert_file']);
            }
            if (!empty($this->config['tls_key_file'])) {
                $settings->setTlsClientCertificateKeyFile($this->config['tls_key_file']);
            }
        }

        $cleanSession = (bool) ($this->config['clean_session'] ?? true);
        $client->connect($settings, $cleanSession);
    }

    /**
     * 发布消息
     * @param string $topic
     * @param string $message
     * @param int|null $qos
     * @param bool|null $retain
     * @throws MqttClientException
     */
    public function publish(string $topic, string $message, ?int $qos = null, ?bool $retain = null): void
    {
        $client = $this->getClient();
        if (!$client->isConnected()) {
            $this->connect();
        }

        $qos = $qos ?? (int) ($this->config['default_qos'] ?? 0);
        $retain = $retain ?? (bool) ($this->config['default_retain'] ?? false);

        $client->publish($topic, $message, $qos, $retain);
    }

    /**
     * 订阅多个主题
     * @param array $topics 主题数组：['a/b' => 0, 'x/y' => 1] 或 ['a/b', 'x/y']
     * @param callable $handler function (string $topic, string $message, bool $retained): void
     * @throws MqttClientException
     */
    public function subscribe(array $topics, callable $handler): void
    {
        $client = $this->getClient();
        if (!$client->isConnected()) {
            $this->connect();
        }

        $defaultQos = (int) ($this->config['default_qos'] ?? 0);

        foreach ($topics as $topic => $qos) {
            if (is_int($topic)) {
                $topic = (string) $qos;
                $qos = $defaultQos;
            }
            $client->subscribe((string) $topic, function (string $t, string $message, bool $retained) use ($handler) {
                $handler($t, $message, $retained);
            }, (int) $qos);
        }
    }

    /**
     * 循环处理（阻塞）——常用于控制台常驻进程
     * @throws MqttClientException
     */
    public function loopForever(): void
    {
        $client = $this->getClient();
        if (!$client->isConnected()) {
            $this->connect();
        }
        $client->loop(true);
    }

    /**
     * 断开连接
     */
    public function disconnect(): void
    {
        if ($this->client instanceof MqttClient && $this->client->isConnected()) {
            try {
                $this->client->disconnect();
            } catch (\Throwable $e) {
                // 忽略断开异常
            }
        }
    }
}


